//
//  BBMeshController.m
//  BBTouch
//
//  Created by ben smith on 8/13/08.
//  This file is part of BBTouch.
//
//  BBTouch is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.

//  BBTouch is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.

//  You should have received a copy of the GNU Lesser General Public License
//  along with BBTouch.  If not, see <http://www.gnu.org/licenses/>.
// 
//  Copyright 2008 Ben Britten Smith ben@benbritten.com .
//

#import "BBMeshController.h"
#import "BBDistortionMesh.h"
#import "BBDistortionMap.h"
#import "BBApplicationSupport.h"
#import "BBConfigurationController.h"
#import "BBUndoController.h"


@implementation BBMeshController

@synthesize distortionMap;
@synthesize meshHeight;
@synthesize meshWidth;

id sharedMeshController;

+(BBMeshController*)sharedMeshController
{
	return sharedMeshController;
}

-(void)awakeFromNib
{
	// load the current meshes from disk (if they exist)
	//later...
	//NSString * cameraMeshFile = [BBApplicationSupport pathForApplicationSupportFileWithName:@"BBCameraMeshData"];
	sharedMeshController = self;
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appQuit:) name:@"NSApplicationWillTerminateNotification" object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appBegin:) name:@"NSApplicationDidFinishLaunchingNotification" object:nil];

	[self addObserver:self forKeyPath:@"distortionMap.sourceMesh.boundingRect" options:NSKeyValueObservingOptionNew context:nil];
	[self addObserver:self forKeyPath:@"distortionMap.destinationMesh.boundingRect" options:NSKeyValueObservingOptionNew context:nil];
	
	[self loadMap:self];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
	distortionMap.dirty = YES;
}

-(void)appBegin:(id)note
{
	if (self.distortionMap != nil) {
		[self.distortionMap calculateMap];
		return; // shunt if we have what we want already (ie a map)
	}
	// make some nice default numbers
	self.meshHeight = 4;
	self.meshWidth = 5;
	[self makeNewMeshes:self];
}

-(IBAction)makeNewMeshes:(id)sender
{
	[[[BBUndoController undoManager] prepareWithInvocationTarget:self] setDistortionMap:self.distortionMap];
	[[BBUndoController undoManager] setActionName:@"New Meshes"];

	
	// no map, make a default for now
	NSSize raw = [[BBConfigurationController sharedConfigurationController] rawImageSize];
	
	// make the default mesh a inset from the raw size
	NSRect screenRect = NSMakeRect(0, 0, raw.width, raw.height);
	NSRect frame = NSInsetRect(screenRect, raw.width/6.0, raw.height/6.0);
	
	BBDistortionMesh * cameraMesh = [BBDistortionMesh meshWithHeight:self.meshHeight width:self.meshWidth boundingRectangle:frame];
	BBDistortionMesh * screenMesh = [BBDistortionMesh meshWithHeight:self.meshHeight width:self.meshWidth boundingRectangle:frame];
	
	BBDistortionMap * map = [[BBDistortionMap alloc] init];
	
	map.mapFrame = screenRect;
	
	map.sourceMesh = cameraMesh;
	map.destinationMesh = screenMesh;
	
	self.distortionMap = map;
	[map release];		
}

// this is the same thing as the bounding rectangle for the 
// source mesh, however we need to guarantee that it is 
// at least one pixel smaller than the source image on all sides
// otherwise the blob detector will barf
// we should probably cache this result and not calc it each time,
// but it gets called very infrequently, so no big
-(NSRect)regionOfInterest
{
	NSRect roi = [self.distortionMap.sourceMesh boundingRect];
	NSRect frame = [self meshFrame];
	
	if (roi.origin.x <= 0) roi.origin.x = 1;
	if (roi.origin.y <= 0) roi.origin.y = 1;
	if (NSMaxX(roi) >= NSWidth(frame)) roi.size.width = NSWidth(frame) - NSMinX(roi) - 1;
	if (NSMaxY(roi) >= NSHeight(frame)) roi.size.height = NSHeight(frame) - NSMinY(roi) - 1;
	
	// the ROI is also flipped in Y (because the camera raw source is flipped
	// from the view coordinates
	roi.origin.y = NSHeight(frame) - NSHeight(roi) - roi.origin.y;
	
	return roi;
}

-(NSRect)meshFrame
{
	return self.distortionMap.mapFrame;
}

-(BBDistortionMesh*)cameraMesh
{
	return self.distortionMap.sourceMesh;
}

-(BBDistortionMesh*)screenMesh
{
	return self.distortionMap.destinationMesh;
}

-(IBAction)recalculateMap:(id)sender
{
	[self.distortionMap calculateMap];
}

-(void)loadFromUnarchivedMap:(BBDistortionMap*)unarchivedMap
{
	if (unarchivedMap != nil) {
		self.distortionMap = unarchivedMap;
		self.meshHeight = self.distortionMap.sourceMesh.height;	
		self.meshWidth = self.distortionMap.sourceMesh.width;	
	}	
}

-(IBAction)loadMap:(id)sender
{
	NSString * meshFile = [BBApplicationSupport pathForApplicationSupportFileWithName:@"BBMeshData"];
	BBDistortionMap * unarchivedMap = [NSKeyedUnarchiver unarchiveObjectWithFile:meshFile];
	[self loadFromUnarchivedMap:unarchivedMap];
}

-(IBAction)saveMap:(id)sender
{
	// save my stuff down to the app support folder
	NSString * supportDir = [BBApplicationSupport applicationSupportFolder];
	NSString * meshFile = [supportDir stringByAppendingPathComponent:@"BBMeshData"];
	BOOL result = [NSKeyedArchiver archiveRootObject:distortionMap toFile:meshFile];

	// TODO: make this nicer
	if (!result)
		[[NSAlert alertWithMessageText:@"Cannot archive Distortion Map!" defaultButton:@"Ok" alternateButton:nil otherButton:nil informativeTextWithFormat:@"There was an error saving to the file: %@",meshFile] runModal];
	
	// now save the XML file in case some other app wants to use it
	NSString * xmlFile = [supportDir stringByAppendingPathComponent:@"BBMeshData.xml"];
	[[distortionMap propertyDictionary] writeToFile:xmlFile atomically:YES]; 
}

-(IBAction)flipCameraMeshHorizontally:(id)sender
{
	// prepare the UNDO invocation with the same method basically
	[[[BBUndoController undoManager] prepareWithInvocationTarget:self.distortionMap.sourceMesh] flipMeshHorizontally];
	[[BBUndoController undoManager] setActionName:@"Flip Mesh Horizontally"];
	
	[self.distortionMap.sourceMesh flipMeshHorizontally];
}

-(IBAction)flipCameraMeshVertically:(id)sender
{
	// prepare the UNDO invocation with the same method basically
	[[[BBUndoController undoManager] prepareWithInvocationTarget:self.distortionMap.sourceMesh] flipMeshVertically];
	[[BBUndoController undoManager] setActionName:@"Flip Mesh Vertically"];
	
	// now do the flip
	[self.distortionMap.sourceMesh flipMeshVertically];
}

-(IBAction)expandCameraMesh:(id)sender
{
	// prepare the UNDO invocation with the current mesh bounding rectangle
	[[[BBUndoController undoManager] prepareWithInvocationTarget:self.distortionMap.sourceMesh]  scaleBoundingRectToNewRect:[self.distortionMap.sourceMesh boundingRect]];
	[[BBUndoController undoManager] setActionName:@"Expand To Edges"];

	// now expand to the mesh frame
	[self.distortionMap.sourceMesh scaleBoundingRectToNewRect:[self.distortionMap mapFrame]];
}

-(IBAction)expandScreenMesh:(id)sender
{
	// prepare the UNDO invocation with the current mesh bounding rectangle
	[[[BBUndoController undoManager] prepareWithInvocationTarget:self.distortionMap.destinationMesh]  scaleBoundingRectToNewRect:[self.distortionMap.destinationMesh boundingRect]];
	[[BBUndoController undoManager] setActionName:@"Expand To Edges"];
	
	// now expand to the mesh frame
	[self.distortionMap.destinationMesh scaleBoundingRectToNewRect:[self.distortionMap mapFrame]];
}

-(void)appQuit:(id)note 
{
	[self saveMap:nil];
}
@end

